module nazo.httpclient;
import std.string, std.stdio, std.socket, nazo.socketstream, std.date, std.zlib, std.conv;

enum gzip{
  FTEXT=1,
  FHCRC=1<<1,
  FEXTRA=1<<2,
  FNAME=1<<3,
  FCOMMENT=1<<4,
}
class HttpClient{
  InternetHost ih;
  InternetAddress ia;
  Socket tcp;
  string host;
  this(string host){
    this(host,80);
  }
  this(string host,ushort port){
    this.host=host;
    ih=new InternetHost;
    ih.getHostByName(host);
    ia = new InternetAddress(ih.addrList[0], port);
  }
  static string GET="GET";
  static string POST="POST";
  static string PUT="PUT";
  static string DELETE="DELETE";
  static string TRACE="TRACE";
  string[string] headers;
  ///Bugs: 303に未対応
  ///Bugs: タイムアウトできない
  ubyte[] download(string path,string method=GET,string data=null,d_time since=d_time.init){
    headers=null;
    tcp = new Socket(ia.addressFamily(),SocketType.STREAM,ProtocolType.TCP);
    tcp.connect(ia);
    auto sock=new SocketStream(tcp);
    scope(exit)sock.close();
    sock.write(cast(ubyte[])(method~" "~path~" HTTP/1.0\r\nHost: "~host~"\r\nUser-Agent: Mozilla/5.0 (X11; U; Linux i686; ja-JP; rv:1.8) Gecko/20051111 Firefox/1.5\r\nAccept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\nAccept-Language: en;q=0.7\r\nAccept-Encoding: deflate\r\nAccept-Charset: utf-8;q=0.7,*;q=0.7\r\nkeep-alive: 300\r\n"~(since==d_time.init?"":"If-Modified-Since: "~toUTCString(since)~"\r\n")~"\r\n"~data));//,gzip
    ubyte[] bf;
    bool alreadyHeaders=false;
    bool first=true;//なるべくフラグは使うなという話だけどつい使ってしまう
    foreach(inout ubyte[] buff;sock){
//      writefln(buff.length);
      bf~=buff;
      int index;
      while(!alreadyHeaders&&(index=(cast(string)bf).find("\r\n"))!=-1){//string型を使うのは良くない方法だけどubyte[]型ではfindが使えないので
        if(first){
          if(bf[9..12]==cast(ubyte[])"304")throw new NotModifiedException;
          if(bf[9..12]!=cast(ubyte[])"200")throw new HttpStatusException(cast(string)bf[9..12]);
          first=false;
          continue;
        }
        string t=cast(string)bf[0..index];
        int i=t.find(": ");
        if(i>0)
          headers[t[0..i]]=t[i+2..$];
//        writef(tmp);
        if(bf.length>=index+4&&bf[index+2..index+4]==cast(ubyte[])"\r\n"){//\r\nの後に\r\nが続く場合
//          writef("Download Headers\n");
          alreadyHeaders=true;
          bf=bf[index+4..$];
          break;
        }
//        writef("%s\n",cast(string)bf[0..index]);
        bf=bf[index+2..$];
      }
//      if(bf.length>=headers["Content-Length"])return;

//      if(cast(string)bf[$-4..$]==)break;
      
//      writef("add buffer:%s ???=%s\n",i++,cast(string)bf[$-4..$]);
    }
    if(auto t="Content-Encoding" in headers){
      if(*t=="deflate")bf=cast(ubyte[])uncompress(bf);
/*      if(*t=="gzip"){
        if(bf[0]!=0x1f||bf[1]!=0x8b)throw new Exception("gzip error");
        if(bf[2]!=8)throw new Exception("gzip error");
        ubyte flag=bf[3];
        //bf[4..8]は時間
        //bf[8]とbf[9]はどっちでもいい。
        uint i=10;
        uint l;
        if(flag&gzip.FEXTRA)i+=2+(cast(uint[])bf[i..i+2])[0];//未テスト
        if(flag&gzip.FNAME)i+=(cast(string)bf[i..$]).find('\0')+1;//未テスト
        if(flag&gzip.FCOMMENT)i+=(cast(string)bf[i..$]).find('\0')+1;//未テスト
        if(flag&gzip.FHCRC){i+=2;l+=8;}//未テスト
        bf=cast(ubyte[])uncompress(bf[i..$-l]);
      }*/
    }
    return bf;
  }
}

class HttpStatusException:Exception{
  int errcode; /// Error code
  string errmsg; /// Error Message
  this(string message){
    errmsg=message;
    super("HttpStatusException: "~message);
  }
  this(int errcode){
    this.errcode=errcode;
    this(std.string.toString(errcode));
  }
  this(string name, int errcode){
    this.errcode=errcode;
    super(name ~ ": " ~ std.string.toString(errcode));
  }
}

class NotModifiedException:Exception{
  this(){
    super("NotModifiedException: ");
  }
}
